6.1 定义
方法是与对象实例绑定的特殊函数。
方法是面向对象编程的基本概念,用于维护和展示对象的自身状态。对象是内敛的,每个实例都有各自不同的独立特征,以属性和方法来暴露对外通信接口。普通函数则专注于算法流程,通过接收参数来完成特定逻辑运算,并返回最终结果。换句话说,方法是有关联状态的,而函数通常没有。
方法和函数定义语法区别的在于前者有前置实例接收参数(receiver),编译器以此确定方法所属类型。在某些语言里,尽管没有显式定义,但会在调用时隐式传递this实例参数。
可以为当前包,以及除接口和指针以外的任何类型定义方法。
type N int
func(n N)toString()string{
return fmt.Sprintf("%#x",n)
}
func main() {
var a N=25
println(a.toString())
}输出:
0x19
方法同样不支持重载(overload)。receiver参数名没有限制,按惯例会选用简短有意义的名称(不推荐使用this、self)。如方法内部并不引用实例,可省略参数名,仅保留类型。
type N int
func(N)test() {
println("hi!")
} 方法可看作特殊的函数,那么receiver的类型自然可以是基础类型或指针类型。这会关系到调用时对象实例是否被复制。
type N int
func(n N)value() { //func value(n N)
n++
fmt.Printf("v: %p, %v\n", &n,n)
}
func(n*N)pointer() { //func pointer(n*N)
(*n)++
fmt.Printf("p: %p, %v\n",n, *n)
}
func main() {
var a N=25
a.value()
a.pointer()
fmt.Printf("a: %p, %v\n", &a,a)
}
输出:
v:0xc8200741c8,26 //receiver被复制
p:0xc8200741c0,26
a:0xc8200741c0,26
可使用实例值或指针调用方法,编译器会根据方法receiver类型自动在基础类型和指针类型间转换。
func main() {
var a N=25
p:= &a
a.value()
a.pointer()
p.value()
p.pointer()
}
输出:
v:0xc82000a2c0,26
p:0xc82000a298,26
v:0xc82000a2f0,27
p:0xc82000a298,27
不能用多级指针调用方法。
func main() {
var a N=25
p:= &a
p2:= &p
p2.value() // 错误:calling method value with receiver p2(type**N)
// requires explicit dereference
p2.pointer() // 错误:calling method pointer with receiver p2(type**N)
// requires explicit dereference
}指针类型的receiver必须是合法指针(包括nil),或能获取实例地址。
type X struct{}
func(x*X)test() {
println("hi!",x)
}
func main() {
var a*X
a.test() // 相当于test(nil)
X{}.test() // 错误:cannot take the address of X literal
}将方法看作普通函数,就能很容易理解receiver的传参方式。
如何选择方法的receiver类型?
- 要修改实例状态,用*\T。
- 无须修改状态的小对象或固定值,建议用T。
- 大对象建议用*\T,以减少复制成本。
- 引用类型、字符串、函数等指针包装对象,直接用T。
- 若包含Mutex等同步字段,用*\T,避免因复制造成锁操作无效。
- 其他无法确定的情况,都用*\T。